import annotations.Queryable
import org.springframework.beans.factory.InitializingBean
import types.Hit
import exception.EntryNotFoundException
import exception.DatabaseError
import util.TypeCastUtil

/**
 * simple service to lookup data from the database
 */
class LookupService implements InitializingBean {

  boolean transactional = true

  def grailsApplication
  def setting

  void afterPropertiesSet() { this.setting = grailsApplication.config.setting }

/**
 * can return 0 - n
 */
  @Queryable(name = "smiles")
  Collection<Compound> lookupBySmile(def value, Map params = [:]) {
    if (value instanceof List) {
      return Compound.executeQuery("Select a from Compound a, Smiles b where a.id = b.compound.id and b.code in (:varList) order by a.id", ["varList": value], params);
    }
    else {
      return Compound.executeQuery("Select a from Compound a, Smiles b where a.id = b.compound.id and b.code = ? order by a.id", [value], params)
    }
  }

  Long countlookupBySmile(def value, Map params = [:]) {
    if (value instanceof List) {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Smiles b where a.id = b.compound.id and b.code in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Smiles b where a.id = b.compound.id and b.code = ?", [value], params)[0])
    }
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "kegg")
  Collection<Compound> lookupByKegg(def value, Map params = [:]) {

    if (value instanceof Collection) {
      def res = Compound.executeQuery("Select a from Compound a, Kegg b where a.id = b.compound.id and b.keggId in (:varList) order by a.id", ["varList": value], params);

      return res
    }
    else {
      return Compound.executeQuery("Select a from Compound a, Kegg b where a.id = b.compound.id and b.keggId = ? order by a.id", [value], params)
    }
  }


  Long countlookupByKegg(def value, Map params = [:]) {

    if (value instanceof Collection) {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Kegg b where a.id = b.compound.id and b.keggId in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Kegg b where a.id = b.compound.id and b.keggId = ?", [value], params)[0])
    }
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "cid")
  Collection<Compound> lookupByCID(def value, Map params = [:]) {

    if (value instanceof Collection) {
      List<Integer> v = TypeCastUtil.getInstance().castIntoInteger(value)
      return Compound.executeQuery("Select a from Compound a, PubchemCompound b where a.id = b.compound.id and b.cid in (:varList) order by a.id", ["varList": v], params)
    }
    else {
      Integer v = TypeCastUtil.getInstance().castIntoInteger(value)
      return Compound.executeQuery("Select a from Compound a, PubchemCompound b where a.id = b.compound.id and b.cid = ? order by a.id", [v], params)
    }
  }

  Long countlookupByCID(def value, Map params = [:]) {

    if (value instanceof Collection) {
      List<Integer> v = TypeCastUtil.getInstance().castIntoInteger(value)
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, PubchemCompound b where a.id = b.compound.id and b.cid in (:varList)", ["varList": v], params)[0])
    }
    else {
      Integer v = TypeCastUtil.getInstance().castIntoInteger(value)
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, PubchemCompound b where a.id = b.compound.id and b.cid = ?", [v], params)[0])
    }
  }

/**
 * can return 0-n
 */
  @Queryable(name = "sid")
  Collection<Compound> lookupBySID(def value, Map params = [:]) {

    if (value instanceof Collection) {
      List<Integer> v = TypeCastUtil.getInstance().castIntoInteger(value)
      return Compound.executeQuery("Select a from Compound a, PubchemSubstance b where a.id = b.compound.id and b.sid in (:varList) order by a.id", ["varList": v], params)
    }
    else {
      Integer v = TypeCastUtil.getInstance().castIntoInteger(value)
      return Compound.executeQuery("Select a from Compound a, PubchemSubstance b where a.id = b.compound.id and b.sid = ? order by a.id", [v], params)
    }
  }

  Long countlookupBySID(def value, Map params = [:]) {

    if (value instanceof Collection) {
      List<Integer> v = TypeCastUtil.getInstance().castIntoInteger(value)
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, PubchemSubstance b where a.id = b.compound.id and b.sid in (:varList)", ["varList": v], params)[0])
    }
    else {
      Integer v = TypeCastUtil.getInstance().castIntoInteger(value)
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, PubchemSubstance b where a.id = b.compound.id and b.sid = ?", [v], params)[0])
    }
  }

/**
 * can return 0-n
 */
  @Queryable(name = "cas")
  Collection<Compound> lookupByCas(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return Compound.executeQuery("Select a from Compound a, Cas b where a.id = b.compound.id and b.casNumber in (:varList) order by a.id", ["varList": value], params);
    }
    else {
      return Compound.executeQuery("Select a from Compound a, Cas b where a.id = b.compound.id and b.casNumber = ? order by a.id", [value], params)
    }
  }

  Long countlookupByCas(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Cas b where a.id = b.compound.id and b.casNumber in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Cas b where a.id = b.compound.id and b.casNumber = ?", [value], params)[0])
    }
  }

/**
 * can return 0 - 1
 */
  Compound lookupByCompoundId(def value, Map params = [:]) {
    Long v = TypeCastUtil.getInstance().castIntoLong(value)
    return Compound.get(v)
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "inchi")
  def lookupByInchi(def value, Map params = [:]) {

    if (value instanceof Collection) {
      return Compound.executeQuery("from Compound a where a.inchi in (:varList) order by a.id", ["varList": value], params)
    }
    else {
      List<Compound> result = Compound.executeQuery("from Compound a where a.inchi = ? order by a.id", [value], params)

      if (result.size() == 1) {
        return result.get(0)
      }
      else if (result.isEmpty()) {
        throw new EntryNotFoundException("no compound found for: ${value}")
      }
      else {
        throw new DatabaseError("database error, there can not be more than 1 compound for an inchie!, offending inchi: $value")
      }
    }
  }


  Long countlookupByInchi(def value, Map params = [:]) {

    if (value instanceof Collection) {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.inchi in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.inchi = ?", [value], params)[0])
    }
  }

  /**
   * can return 0 - n
   */
  @Queryable(name = "formula")
  Collection<Compound> lookupByFormula(def value, Map params = [:]) {

    if (value instanceof Collection) {
      return Compound.executeQuery("from Compound a where a.formula in (:varList) order by a.id", ["varList": value], params)
    }
    else {
      return Compound.executeQuery("from Compound a where a.formula = ? order by a.id", [value], params)
    }
  }

  Long countlookupByFormula(def value, Map params = [:]) {

    if (value instanceof Collection) {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.formula in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.formula = ?", [value], params)[0])
    }
  }

  /**
   * can return 0 - n
   */
  @Queryable(name = "mass")
  Collection<Compound> lookupByExactMass(def value, Map params = [:]) {

    if (value instanceof Collection) {
      List<Double> v = TypeCastUtil.getInstance().castIntoDouble(value)
      return Compound.executeQuery("from Compound a where a.exactMolareMass in (:varList) order by a.id", ["varList": v], params)
    }
    else {
      Double v = TypeCastUtil.getInstance().castIntoDouble(value)
      return Compound.executeQuery("from Compound a where a.exactMolareMass = ? order by a.id", [v], params)
    }
  }

  Long countlookupByExactMass(def value, Map params = [:]) {

    if (value instanceof Collection) {
      List<Double> v = TypeCastUtil.getInstance().castIntoDouble(value)
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.exactMolareMass in (:varList)", ["varList": v], params)[0])
    }
    else {
      Double v = TypeCastUtil.getInstance().castIntoDouble(value)
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.exactMolareMass = ?", [v], params)[0])
    }
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "inchikey")
  def lookupByInchiKey(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return Compound.executeQuery("from Compound a where a.inchiHashKey.completeKey in (:varList) order by a.id", ["varList": value], params);
    }
    else {
      List<Compound> result = Compound.executeQuery("from Compound a where a.inchiHashKey.completeKey = ? order by a.id", [value], params)

      if (result.size() == 1) {
        return result.get(0)
      }
      else if (result.isEmpty()) {
        throw new EntryNotFoundException("no compound found for: ${value}")
      }
      else {
        throw new DatabaseError("database error, there can not be more than 1 compound for an inchikey!, offending inchi: $value")
      }
    }
  }

  Long countlookupByInchiKey(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.inchiHashKey.completeKey in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.inchiHashKey.completeKey = ?", [value], params)[0])
    }
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "skeleton")
  Collection<Compound> lookupByPartialInchiKey(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return Compound.executeQuery("from Compound a where a.inchiHashKey.firstBlock in (:varList) order by a.id", ["varList": value], params);
    }
    else {
      return Compound.executeQuery("from Compound a where a.inchiHashKey.firstBlock = ? order by a.id", [value], params)
    }
  }

  Long countlookupByPartialInchiKey(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.inchiHashKey.firstBlock in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("select count(*) from Compound a where a.inchiHashKey.firstBlock = ?", [value], params)[0])
    }
  }

  @Queryable(name = "name")
  Collection<Compound> lookupByName(def value, Map params = [:]) {
    def val = TypeCastUtil.getInstance().toLowerCase(value);
    if (val instanceof Collection) {
      Set temp = new HashSet()

      boolean wildCard = false
      val.each {String s ->
        String v = convertToLikeWildCard(s)
        temp.add(v)

        if (v.contains("%") | v.contains("_")) {
          wildCard = true
        }

      }

      if (wildCard == false) {
        return Compound.executeQuery("Select distinct a from Compound a, Synonym b where a.id = b.compound.id and LOWER(b.name) in (:varList) order by a.id", ["varList": val], params)
      }
      else{
        Collection<Compound> result = new HashSet<Compound>()

        temp.each{String s ->
          Collection<Compound> search = lookupByName(s,params)
          
          result.addAll(search)
        }

        return result
      }
    }
    else {
      val = convertToLikeWildCard(val)
      return Compound.executeQuery("Select distinct a from Compound a, Synonym b where a.id = b.compound.id and LOWER(b.name) like ? order by a.id", [val], params)
    }
  }

  /**
   *
   * converts a lucense query string to a like string, or attemps to. It's a rather simple version of doing this
   * @param
   value
   * @return
   */
  String convertToLikeWildCard(def value) {

    log.debug("convert wildcards for ${value}")

    if (value.contains("*")) {
      value = value.replaceAll("\\*", "%")
    }
    if (value.contains("?")) {
      value = value.replaceAll("\\?", "_")
    }

    log.debug("done - ${value}")

    return value
  }

  Long countlookupByName(def value, Map params = [:]) {
    def val = TypeCastUtil.getInstance().toLowerCase(value);

    if (val instanceof Collection) {
      return (Long) (Compound.executeQuery("Select count(a.id) from Compound a, Synonym b where a.id = b.compound.id and LOWER(b.name) in (:varList)", ["varList": val])[0])
    }
    else {
      val = this.convertToLikeWildCard(val)
      return (Long) (Compound.executeQuery("Select count(a.id) from Compound a, Synonym b where a.id = b.compound.id and LOWER(b.name) like ?", [val])[0])
    }
  }


  @Queryable(name = "hmdb")
  Collection<Compound> lookupByHMDB(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return Compound.executeQuery("Select a from Compound a, HMDB b where a.id = b.compound.id and b.hmdbId in (:varList) order by a.id", ["varList": value], params);
    }
    else {
      return Compound.executeQuery("Select a from Compound a, HMDB b where a.id = b.compound.id and b.hmdbId = ? order by a.id", [value], params)
    }
  }

  Long countlookupByHMDB(def value, Map params = [:]) {
    if (value instanceof Collection) {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, HMDB b where a.id = b.compound.id and b.hmdbId in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, HMDB b where a.id = b.compound.id and b.hmdbId = ?", [value])[0])
    }
  }

  @Queryable(name = "lipidmap")
  Collection<Compound> lookupByLipidMapId(def value, Map params = [:]) {
    if (value instanceof List) {
      return Compound.executeQuery("Select a from Compound a, LipidMap b where a.id = b.compound.id and b.lipidMapId in (:varList) order by a.id", ["varList": value], params);
    }
    else {
      return Compound.executeQuery("Select a from Compound a, LipidMap b where a.id = b.compound.id and b.lipidMapId = ? order by a.id", [value], params)
    }
  }

  Long countlookupByLipidMapId(def value, Map params = [:]) {
    if (value instanceof List) {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, LipidMap b where a.id = b.compound.id and b.lipidMapId in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, LipidMap b where a.id = b.compound.id and b.lipidMapId = ?", [value])[0])
    }
  }


  @Queryable(name = "chebi")
  Collection<Compound> lookupByChebiId(def value, Map params = [:]) {
    if (value instanceof List) {
      return Compound.executeQuery("Select a from Compound a, Chebi b where a.id = b.compound.id and b.chebiId in (:varList) order by a.id ", ["varList": value], params);
    }
    else {
      return Compound.executeQuery("Select a from Compound a, Chebi b where a.id = b.compound.id and b.chebiId = ? order by a.id", [value], params)
    }
  }

  Long countlookupByChebiId(def value, Map params = [:]) {
    if (value instanceof List) {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Chebi b where a.id = b.compound.id and b.chebiId in (:varList)", ["varList": value], params)[0])
    }
    else {
      return (Long) (Compound.executeQuery("Select count(*) from Compound a, Chebi b where a.id = b.compound.id and b.chebiId = ?", [value])[0])
    }
  }

/**
 * looks up the compound for a single hit
 * can return 0 - n
 */
  Collection<Compound> lookupByHit(Hit hit, Map params = [:]) {
    //contains our results
    Collection<Compound> result = new HashSet<Compound>()

    //find out which type our hit is
    switch (hit.type) {
      case hit.INCHI:
        log.debug("detected inchi...")
        result.add(lookupByInchi(hit.value, params))
        break
      case hit.INCHI_KEY:
        log.debug("detected inchi key...")

        result.add(lookupByInchiKey(hit.value, params))
        break
      case hit.CAS:
        log.debug("detected cas...")

        result = lookupByCas(hit.value, params)
        break
      case hit.KEGG:
        log.debug("detected kegg...")

        result = lookupByKegg(hit.value, params)
        break
      case hit.COMPOUND_ID:
        log.debug("detected compound id...")

        result.add(lookupByCompoundId(hit.compoundId))
        break
      case hit.OSCAR:
        log.debug("detected oscar hit...")

        result = lookupByName(hit.value, params)
        break

      default:
        break
    }

    //print our model
    log.debug("model: ${result}")

    //return the result
    return result
  }

}
